import time
import numpy as np

from logger import get_logger
from config import config
from process import Process
from utils import OrderQueue
from detect.detect import CLASSES
from .object_tracer import ObjectTracer
from .triggers import *
from .region import Region


class AlertExecutor:

    def __init__(self, config):
        self._tracking_objs = None
        self._logger = get_logger('alert')
        self._regions = {}

        if region_cfg := config.ai_region:
            for region in region_cfg:
                polies = np.array(region['region']).reshape(-1, 2)
                polies[:, 0] *= config.stream_w
                polies[:, 1] *= config.stream_h
                polies = polies.astype(np.int32)
                if region['name'] in self._regions:
                    self._logger.warning(f'区域[{region["name"]}]已存在同名区域，新区域将覆盖旧区域位置')
                self._regions[region['name']] = Region(region['name'], polies)
                self._logger.debug(f'区域[{region["name"]}]已添加，区域为：\n{polies}')
        self._regions[None] = Region(None)
        
        if alert_cfg := config.alert:
            self._tracking_objs = set()
            # 下面的循环就是为了处理成上面这种格式
            for alert in alert_cfg:
                obj: str = alert['object']
                if obj not in CLASSES:
                    self._logger.warning(f'物体[{obj}]不在该模型可检测物体的列表中，将忽略事件[{alert["event"]}]...')
                    continue
                try:
                    trigger = parse_trigger(alert)
                except Exception as e:
                    self._logger.warning(f'事件[{alert["event"]}]添加失败：{str(e)}')
                    continue
                if trigger.region not in self._regions:
                    self._logger.warning(f'事件[{trigger.event}]添加失败，所指定的区域[{trigger.region}]不存在')
                    continue
                self._regions[trigger.region].add_trigger(trigger)
                self._tracking_objs.add(obj)
                
                self._logger.debug(f'事件[{trigger.event}]已添加，关联对象为[{trigger.object}]，对应区域为：[{trigger.region if trigger.region else "整个画面"}]')

            # 处理成{类别1: 空集合, 类别2: 空集合...}的格式
            self._tracking_objs = {obj: set() for obj in self._tracking_objs}
            self._exist_objs = {obj: set() for obj in self._tracking_objs}

            if not self._tracking_objs:
                self._tracking_objs = self._exist_objs = None
            else:
                self._logger.info(f'检测到有效报警事件，将对物体:{list(self._tracking_objs.keys())}进行目标追踪')

        if not self._tracking_objs:
            self._logger.info('没有有效的报警事件，将不对任何物体执行跟踪算法')
        
        self._regions = [r for r in self._regions.values() if not r.empty()]
        self._logger.debug(f'共添加{len(self._regions)}个有效区域')

    def update(self, image, results):
        if not self._tracking_objs:
            return
        current_time = time.perf_counter()

        # 获取所有正在追踪的物体，之后排查谁没出现时方便
        notfound_objs = set().union(*self._tracking_objs.values())
        obj_and_action = []

        def process_action(action, obj):
            # 存储动作等待后续处理
            if action == ObjectTracer.Action.ENTER or action == ObjectTracer.Action.LEAVE:
                obj_and_action.append((action, obj))
            # 处理物体集合
            if action == ObjectTracer.Action.ENTER:
                self._exist_objs[obj.label].add(obj)
            elif action == ObjectTracer.Action.LEAVE:
                self._exist_objs[obj.label].discard(obj)
                self._tracking_objs[obj.label].discard(obj)
            elif action == ObjectTracer.Action.FAKE:
                self._tracking_objs[obj.label].discard(obj)

        for box, label_idx, _ in results:
            # 找到对应的类型
            label = CLASSES[label_idx]

            if (obj_set := self._tracking_objs.get(label)) is None:
                continue

            max_sim = 0
            max_sim_obj = None
            for obj in obj_set:
                # 如果这个物体被认领了就直接跳过
                if obj not in notfound_objs:
                    continue
                sim = obj.sim(box)
                # 找最大位置相似度物体，并且必须大于最小位置相似度
                if sim > max_sim and sim > ObjectTracer.TRACE_MIN_SIM:
                    max_sim = sim
                    max_sim_obj = obj
            if max_sim_obj is not None:
                # 可以找到之前追踪的物体
                notfound_objs.discard(max_sim_obj)
                action = max_sim_obj.update(current_time, box, image)
                # 判断是否是进入或者离开的物体
                process_action(action, max_sim_obj)
            else:
                # 那就新建一个物体追踪器
                obj_set.add(ObjectTracer(label, box, image))
        
        # 没有被认领的物体空更新一次
        for obj in notfound_objs:
            process_action(obj.update(current_time), obj)
        
        # 到这里，所有的集合都更新完毕，下面进行触发器的更新
        # 更新每个区域的现有物体状态
        for objs in self._exist_objs.values():
            for obj in objs:
                for region in self._regions:
                    region.update_object(obj, image)

        # 更新离开物体的状态
        for action, obj in obj_and_action:
            # 判断是否是进入或者离开的物体
            if action == ObjectTracer.Action.LEAVE:
                # 从整个画面里离开了，那一定从画面的区域里离开了
                for region in self._regions:
                    region.update_object(obj, image, True)

        # 所有区域更新update回调
        for region in self._regions:
            region.update(image)

class Alerter(Process):

    def __init__(self, config, alert_queue):
        super().__init__()
        self._add_process(config, alert_queue)
        self._start()

    def _run(self, config, alert_queue):
        alerter = AlertExecutor(config)
        if config.multiprocess:
            queue = OrderQueue(alert_queue)
        else:
            queue = alert_queue
        del config
    
        while True:
            # results大概长这样：[(<int x1>, <int y1>, <int x2>, <int y2>), <int 类别编号>, <float 置信度>), ...]
            _, frame, results = queue.get()
            if frame is not None and results is not None:
                alerter.update(frame, results)  # frame是opencv的图像，因为报警时需要个base64截图，所以图像也传过来了
